﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace AVLTutorial
{
    public class VisualNode
    {
        public bool isInUse;

        // the data that this visual is representing
        int data;
        // getter and setter for our data link
        public int Data { get { return data; } }

        // the parent node of this node
        // if this node is the root, this is NULL
        VisualNode parentNode;
        public VisualNode ParentNode { get { return parentNode; } set { parentNode = value; } }

        // transform pointing to where we are located
        // this is scaled by masterscale and transformed by the root node position
        TranslateTransform translateTransform;

        
        // our current instructed destination
        // the X any Y we are travelling to
        double destX, destY;

        // a line leading from us to our parent
        // if we are the root, this will be NULL
        Line newLine;

        Ellipse newEllipse;
        Label newLabel;

        DateTime creationTime;

        /// <summary>
        /// Instructs this visual node to move to new coordinates.
        /// </summary>
        /// <param name="newX">New X</param>
        /// <param name="newY">New Y</param>

        public void setDestination(double newX, double newY)
        {
            destX = newX;
            destY = newY;
        }
        /// <summary>
        /// Creates a NEW visual node.
        /// </summary>
        /// <param name="mainPage"></param>
        /// <param name="mainCanvas"></param>
        /// <param name="startX"></param>
        /// <param name="startY"></param>
        /// <param name="data"></param>

        public VisualNode(MainPage mainPage, Canvas mainCanvas, double startX, double startY, int data)
        {
            this.data = data;

            SolidColorBrush black = new SolidColorBrush(Color.FromArgb(255, 0, 0, 0));

            // create an ellipse to represent the data
            newEllipse = new Ellipse();
            newEllipse.Width = 35;
            newEllipse.Height = 35;

            TransformGroup mainLocation = new TransformGroup();

            // create the translate transform that will position this node
            translateTransform = new TranslateTransform();

            translateTransform.X = startX;
            translateTransform.Y = startY;

            
            
            mainLocation.Children.Add(translateTransform);
            mainLocation.Children.Add(mainPage.masterScale);
            mainLocation.Children.Add(mainPage.rootPositon);

            // apply the transform to the ellipse
            // note that if the transform changes, the ellipse will move
            newEllipse.RenderTransform = mainLocation;

            // set our stroke so the ellipse actually shows up
            newEllipse.Stroke = black;

            // add it to the actual UI
            mainCanvas.Children.Add(newEllipse);

            // create a text box to show the data itself
            newLabel = new Label();

            newLabel.Content = data;

            // here we're doing an offset from the previous transform
            TranslateTransform translationOffset = new TranslateTransform();

            // this is about right for this
            translationOffset.X = 10 * mainPage.masterScale.ScaleX;
            translationOffset.Y = 10 * mainPage.masterScale.ScaleX;

            // transform groups can bring together multiple sub-transforms. 
            // The transforms are performed in order, which can become relevant if we're doing rotation transforms, but doesn't matter here
            TransformGroup textTransforms = new TransformGroup();

            // add the two transforms to the transform group
            
            textTransforms.Children.Add(translateTransform);
            textTransforms.Children.Add(translationOffset);
            textTransforms.Children.Add(mainPage.masterScale);
            textTransforms.Children.Add(mainPage.rootPositon);

            // set the render transform. this works like above, but both the transforms apply as they are in the transform group
            newLabel.RenderTransform = textTransforms;

            // add the label to the root of the scene
            mainCanvas.Children.Add(newLabel);

            creationTime = DateTime.Now;
        }

        /// <summary>
        /// This method tells the VisualNode to destroy its visual representation
        /// If the node has not been removed from the list, crashes and/or weird behavior is likeley.
        /// </summary>

        public void destroy(Canvas mainCanvas)
        {
            mainCanvas.Children.Remove(newEllipse);
            mainCanvas.Children.Remove(newLabel);
            if(newLine != null) mainCanvas.Children.Remove(newLine);
        }

        /// <summary>
        /// Should be called every time dest is changed, so that we can figure out how fast to move
        /// </summary>

        public void setMovementModifier(MainPage mainPage)
        {
            double dist = Math.Sqrt(Math.Pow(translateTransform.X - destX, 2) + Math.Pow(translateTransform.Y - destY, 2));

            moveModifier = Math.Max(dist, MIN_DIST) / mainPage.SpeedSlider.Value * 0.016;
        }

        /// <summary>
        /// Should be called every time a new dest is set, so that we can decide if we need to veer to avoid another node
        /// </summary>
        /// <param name="mainPage">The application's MainPage.</param>

        public void setVeerAmount(MainPage mainPage)
        {
            /*foreach (VisualNode visual in mainPage.visualNode)
            {
                if (Math.Abs(destX - visual.translateTransform.X) +
                    Math.Abs(destY - visual.translateTransform.Y) < 2)
                {
                    veerAmount = Math.PI / 4;
                    return;
                }
            }*/
            veerAmount = 0.2;
        }

        // this specifies how far off of the calculated angle to our destination we should travel
        // values higher than PI/4 seem to cause "whirlpooling"
        // >= PI/2 will cause it to orbit its destination, or worse
        double veerAmount = 0;

        // how fast we should be moving
        // set by setMoveModifier
        double moveModifier = 1;

        // constant that specifies how long a move should take, at maximum
        const double MOVE_DURATION = 1;

        // sets a minimum distance for the purpose of calculating movement speed,
        // as we dont want nodes that barely have to move to move really slow
        const double MIN_DIST = 20;

        /// <summary>
        /// Should be called each frame to move this visual towards its destination.
        /// </summary>
        /// <param name="mainPage"></param>
        /// <param name="mainCanvas"></param>

        public bool update(MainPage mainPage, Canvas mainCanvas, double elapsedTime)
        {
            bool didMove = false;

            if (translateTransform.X > mainPage.ActualWidth * 0.4)
                mainPage.WantedScale = Math.Min(mainPage.WantedScale, mainPage.ActualWidth * 0.4 / translateTransform.X);
            if (translateTransform.X < mainPage.ActualWidth * -0.4)
                mainPage.WantedScale = Math.Min(mainPage.WantedScale, mainPage.ActualWidth * -0.4 / translateTransform.X);

            // calculate how fast we should be moving
            // this will be 1 if we're running at about 60 FPS
            // this is so that if the FPS drops, things will move in big chunks instead of moving slowly
            double moveFactor = elapsedTime / 0.0166666;

            // figure the distance to our destination
            double distance = Math.Sqrt(Math.Pow(destX - translateTransform.X, 2) + Math.Pow(destY - translateTransform.Y, 2));

            double oldX = translateTransform.X;
            double oldY = translateTransform.Y;

            // if there is any point in moving
            if (distance > moveModifier * moveFactor)
            {
                // calculate the direction we need to travel to reach our destination
                double direction = Math.Atan2(destY - translateTransform.Y, destX - translateTransform.X);

                // calculate our facing based on the direction plus the amount we're veering
                double facing = direction + veerAmount;

                // move our translate transforms in the facing
                translateTransform.X += Math.Cos(facing) * moveModifier * moveFactor;
                translateTransform.Y += Math.Sin(facing) * moveModifier * moveFactor;
            }

            foreach (VisualNode visual in mainPage.visualNode)
            {
                if (visual == this) continue;

                double dist = Math.Sqrt(Math.Pow(visual.translateTransform.X - translateTransform.X, 2) + Math.Pow(visual.translateTransform.Y - translateTransform.Y, 2));

                double moveX = (visual.translateTransform.X - translateTransform.X) / dist;
                double moveY = (visual.translateTransform.Y - translateTransform.Y) / dist;

                if (dist < 35)
                {
                    visual.translateTransform.X -= moveX * (dist - 35) / 2;
                    visual.translateTransform.Y -= moveY * (dist - 35) / 2;
                    translateTransform.X += moveX * (dist - 35) / 2;
                    translateTransform.Y += moveY * (dist - 35) / 2;
                }
            }

            if (Math.Sqrt(Math.Pow(oldX - translateTransform.X, 2) + Math.Pow(oldY - translateTransform.Y, 2)) >
                (moveModifier * moveFactor) / 2)
                didMove = true;

            if (parentNode != null)
            {
                // if we DO have a parent, but our line is null...
                if (newLine == null)
                {
                    newLine = new Line();
                    mainCanvas.Children.Add(newLine);

                    newLine.Stroke = new SolidColorBrush(Color.FromArgb(255, 0, 0, 0));
                }
                // find the distance to our parent node
                double dist = Math.Sqrt(Math.Pow(translateTransform.X - parentNode.X, 2) + Math.Pow(translateTransform.Y - parentNode.Y, 2));

                // set the first X and Y to the edge of our circle. note that the radius of our circle is 17.5
                newLine.X1 = translateTransform.X + 17.5 + ((parentNode.X - translateTransform.X) / dist * 17.5);
                newLine.Y1 = translateTransform.Y + 17.5 + ((parentNode.Y - translateTransform.Y) / dist * 17.5);

                // set the second X and Y to the edge of our parent's circle
                newLine.X2 = parentNode.X + 17.5 + ((translateTransform.X - parentNode.X) / dist * 17.5);
                newLine.Y2 = parentNode.Y + 17.5 + ((translateTransform.Y - parentNode.Y) / dist * 17.5);

                // apply scaling factors to this line
                newLine.X1 *= mainPage.masterScale.ScaleX;
                newLine.X2 *= mainPage.masterScale.ScaleX;
                newLine.Y1 *= mainPage.masterScale.ScaleY;
                newLine.Y2 *= mainPage.masterScale.ScaleY;

                // scale the width of the line
                newLine.StrokeThickness = mainPage.masterScale.ScaleX;

                // translate us based on the root position
                // note that this is performed *after* the scaling, so the scaling does not apply to it
                newLine.X1 += mainPage.rootPositon.X;
                newLine.X2 += mainPage.rootPositon.X;
                newLine.Y1 += mainPage.rootPositon.Y;
                newLine.Y2 += mainPage.rootPositon.Y;
            }
            else if (newLine != null)
            {
                // this is called if we are now the root and we used to not be
                // this is a very rare case, but we need to remove our line, as we no longer have a parent
                mainCanvas.Children.Remove(newLine);
                newLine = null;
            }

            if (creationTime.AddMilliseconds(900) > DateTime.Now)
            {
                newEllipse.StrokeThickness = 5;
            }
            else
            {
                newEllipse.StrokeThickness = 1;
            }

            //newEllipse.StrokeThickness = 30000000 - DateTime.Now - creationTime.Ticks

            return didMove;
        } 

        public double X { get { return translateTransform.X; } }
        public double Y { get { return translateTransform.Y; } }
    }
}
